In [1]:
import ipywidgets as widgets

from ipyniivue import NiiVue, download_dataset
from ipyniivue.download_dataset import DATA_FOLDER

# GitHub API URL for the base folder
BASE_API_URL = "https://niivue.com/demos/images/"
In [2]:
# Download data for example
download_dataset(
    BASE_API_URL,
    DATA_FOLDER,
    files=[
        "mni152.nii.gz",
        "hippo.nii.gz",
        "pcasl.nii.gz",
    ],
)
mni152.nii.gz already exists.
hippo.nii.gz already exists.
pcasl.nii.gz already exists.
Dataset downloaded successfully to /Users/chrisdrake/Dev/ipyniivue/src/ipyniivue/images.

Mosaic strings¶

Mosaic coordinates are preceded by one or more labels. The first label is orientation:

A: axial C: coronal S: sagittal

Numbers after the orientation label indicate slices in the real-world coordinate system, defined by the volume's affine transformation matrix. So the string:
A 10.0 20.4 30
would indicate axial slices at 10.0, 20.4, and 30 mm.

Additional labels may be used to render a volume (R) and to show the location of other slices in the mosaic (X). In the case of a volume render, the value of the coordinate is ignored and the sign indicates the view orientation.

This first example is based on https://niivue.github.io/niivue/features/mosaic.html¶

Note that this example does NOT show orientation labels, and L/R might not be what you expect.

In [3]:
volumes = [
    {
        "path": DATA_FOLDER / "mni152.nii.gz",
        "colormap": "gray",
        "visible": True,
        "opacity": 1.0,
    },
    {
        "path": DATA_FOLDER / "hippo.nii.gz",
        "colormap": "red",
        "visible": True,
        "opacity": 1,
    },
]
nv = NiiVue()
nv.load_volumes(volumes)


style = {"description_width": "initial"}
string_widget = widgets.Text(
    value="A -20 50 60 70 C -10 -20 -50; S R X 0 R X -0",
    description="Slice mosaic string",
    style=style,
)
string_widget.layout.width = "auto"

nv.opts.slice_mosaic_string = string_widget.value


def update_view(*args):
    """Update the mosaic according to the string in the widget."""
    nv.opts.slice_mosaic_string = string_widget.value


string_widget.observe(update_view, "value")

display(string_widget)
display(nv)
/Users/chrisdrake/Dev/ipyniivue/src/ipyniivue/widget.py:1327: UserWarning: Ignored unsupported kwargs in Volume: ['visible']
  volume_objects.append(Volume(**item))

Full mosaic demo¶

For this, we'll use nibabel to get the dimensions of the volume we're looking at. Since nibabel isn't currently in the ipyniivue requirements, you might need to install it.

In [4]:
try:
    import nibabel as nib
except ModuleNotFoundError:
    !pip install nibabel
    import nibabel as nib

import numpy as np
In [5]:
orientations = {"sagittal": 0, "coronal": 1, "axial": 2}
In [6]:
def get_coords(image_file):
    """Get coordinates for the slices in each orientation."""
    img = nib.load(image_file)
    coords = {}
    for label, axis in orientations.items():
        coords[label] = [
            x * img.affine[axis][axis] + img.affine[axis][3]
            for x in range(0, img.shape[axis])
        ]
    return coords
In [7]:
def full_mosaic(coords, ncols=None, step=1, orientation="axial"):
    """
    Generate a mosaic string.

    Args:
    coords: coordinates for the slices in each orientation
    ncols: number of columns for display, default: sqrt of shape
    step: display every Nth slice, default: 1
    orientation: orientation of slices, should be in the orientations dict above
    """
    prefix = orientation[0].upper()
    if not ncols:
        ncols = int(np.sqrt(len(coords[orientation])))

    ms = ""
    for i, x in enumerate(range(0, len(coords[orientation]), step)):
        if not i % ncols:
            if ms == "":
                ms = ms + f"{prefix} "
            else:
                ms = ms + f"; {prefix} "
        ms = ms + f"{coords[orientation][x]:.02f} "
    return ms
In [8]:
# Putting it all together
# By default ipyniivue has a widget height = 300.
# We'll put that on a slider to let us adjust the image size.

t1_image_file = DATA_FOLDER / "mni152.nii.gz"
coords = get_coords(t1_image_file)

volumes = [
    {
        "path": t1_image_file,
        "colormap": "gray",
        "visible": True,
        "opacity": 1.0,
    },
]
nv2 = NiiVue()
nv2.load_volumes(volumes)

# Start by showing every 5th slice, with 10 columns
init_cols = 10
init_step = 5
nv2.opts.slice_mosaic_string = full_mosaic(coords, ncols=init_cols, step=init_step)

axis_selector = widgets.Dropdown(
    options=["axial", "sagittal", "coronal"], description="View"
)
column_slider = widgets.IntSlider(min=0, max=20, value=init_cols, description="Columns")
step_slider = widgets.IntSlider(min=1, max=20, value=init_step, description="Steps")
size_slider = widgets.IntSlider(min=100, max=1000, value=300, description="Height")


def update_mosaic(*args):
    """Update the mosaic string."""
    nv2.opts.slice_mosaic_string = full_mosaic(
        coords,
        step=step_slider.value,
        ncols=column_slider.value,
        orientation=axis_selector.value,
    )


def update_height(*args):
    """Update widget height."""
    nv2.height = size_slider.value


column_slider.observe(update_mosaic, "value")
step_slider.observe(update_mosaic, "value")
axis_selector.observe(update_mosaic, "value")
size_slider.observe(update_height, "value")

display(column_slider)
display(step_slider)
display(size_slider)
display(axis_selector)
display(nv2)
/Users/chrisdrake/Dev/ipyniivue/src/ipyniivue/widget.py:1327: UserWarning: Ignored unsupported kwargs in Volume: ['visible']
  volume_objects.append(Volume(**item))

4D mosaic viewer¶

In [9]:
example4d = DATA_FOLDER / "pcasl.nii.gz"
coords4d = get_coords(example4d)

volumes = [
    {
        "path": example4d,
        "colormap": "gray",
        "visible": True,
        "opacity": 1.0,
    },
]
nv3 = NiiVue()
nv3.load_volumes(volumes)

nvols = nib.load(example4d).shape[-1]
init_cols = np.ceil(np.sqrt(nib.load(example4d).shape[2]))

nv3.opts.slice_mosaic_string = full_mosaic(coords4d, ncols=init_cols)

column_slider4d = widgets.IntSlider(
    min=0, max=20, value=init_cols, description="Columns"
)
vol_slider4d = widgets.IntSlider(min=0, max=nvols - 1, description="Volume")
size_slider4d = widgets.IntSlider(min=100, max=1000, value=300, description="Height")


def update_mosaic4d(*args):
    """Update mosaic string."""
    nv3.volumes[0].frame4D = vol_slider4d.value
    nv3.opts.slice_mosaic_string = full_mosaic(coords4d, ncols=column_slider4d.value)


def update_view4d(*args):
    """Update widget height."""
    nv3.height = size_slider4d.value


column_slider4d.observe(update_mosaic4d, "value")
vol_slider4d.observe(update_mosaic4d, "value")
size_slider4d.observe(update_view4d, "value")

display(column_slider4d)
display(vol_slider4d)
display(size_slider4d)
display(nv3)